package org.gw.ylc.base.data;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.TypedQuery;

import org.gw.ylc.base.data.model.Permission;
import org.gw.ylc.base.data.model.PropertyDef;
import org.gw.ylc.base.data.model.QFilter;
import org.gw.ylc.base.repositories.CommonRepository;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.SingleConnectionDataSource;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import com.googlecode.aviator.AviatorEvaluator;

@Component
public class PermissionContext implements InitializingBean {
	public static final String SCHOOL_ADMIN = "school";
	public static final String DEPARTMENT_ADMIN = "department";
	public static final String TEAM_ADMIN = "team";
	public static final String STUDENT = "student";
	public static final String TOURIST = "tourist";
	public static final String TEACHER = "teacher";
	public static final String BRANCH_ADMIN = "branch";
	// role -> entity
	private Map<String, Map<String, EntityPermission>> entityPermissionMap = new HashMap<String, Map<String, EntityPermission>>();
	private boolean strict = false;

	private static ThreadLocal<String> ROLECONTEXT = new ThreadLocal<String>();
	private static ThreadLocal<Map<String, Object>> ENVCONTEXT = new ThreadLocal<Map<String, Object>>();
	private static PermissionContext instance;
	private String dbFile = "classpath*:/permission";
	private String uri;

	public PermissionContext() {
		instance = this;
	}

	public static PermissionContext getInstance() {
		return instance;
	}

	public static void setCurrentRole(String role) {
		// List<String> list = Arrays.asList(role);
		ROLECONTEXT.set(role);
	}

	public static void clearCurrentRole() {
		ROLECONTEXT.remove();
	}

	public static void setEnvironment(Map<String, Object> env) {
		ENVCONTEXT.set(env);
	}

	public static void clearEnvironment() {
		ENVCONTEXT.remove();
	}

	public EntityPermission getPermission(String entity) {
		Map<String, EntityPermission> map = entityPermissionMap.get(ROLECONTEXT.get());
		return map != null ? map.get(entity) : null;
	}

	public EntityPermission getPermission(String role, String entity) {
		Map<String, EntityPermission> map = entityPermissionMap.get(role);
		return map != null ? map.get(entity) : null;
	}

	public boolean isStrict() {
		return strict;
	}

	public void setStrict(boolean strict) {
		this.strict = strict;
	}

	public void setPermission(EntityPermission permission) {
		String role = permission.getRole();
		Map<String, EntityPermission> map = entityPermissionMap.get(role);
		if (map == null) {
			map = new HashMap<String, EntityPermission>();
			entityPermissionMap.put(role, map);
		}
		map.put(permission.getEntity(), permission);
	}

	public List<EntityPermission> getPermissions() {
		Collection<Map<String, EntityPermission>> values = entityPermissionMap.values();
		List<EntityPermission> permissions = new ArrayList<>();
		for (Map<String, EntityPermission> m : values) {
			permissions.addAll(m.values());
		}
		return permissions;
	}

	public static Object eval(String expr) {
		return AviatorEvaluator.execute(expr, ENVCONTEXT.get());
	}

	public static Object eval(String expr, String key, Object value) {
		Map<String, Object> map = new HashMap<String, Object>();
		map.put(key, value);
		return AviatorEvaluator.execute(expr, map);
	}

	public static Object eval(String expr, Map<String, Object> env) {
		Map<String, Object> map = ENVCONTEXT.get();
		if (env != null) {
			for (Entry<String, Object> en : map.entrySet()) {
				if (!env.containsKey(en.getKey())) {
					env.put(en.getKey(), en.getValue());
				}
			}
		} else {
			env = map;
		}
		return AviatorEvaluator.execute(expr, env);
	}

	private void loadFromDB() throws ClassNotFoundException, IOException {
		PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();

		Resource resource = resolver.getResource(dbFile + ".mv.db");
		if (!resource.exists()) {
			Resource[] resources = resolver.getResources(dbFile + ".mv.db");
			if (resources.length > 0) {
				resource = resources[0];
			} else {
				return;
			}
		}
		Class.forName("org.h2.Driver");
		SingleConnectionDataSource dataSource = null;
		try {
			this.uri = resource.getURI().toString();
			uri = uri.substring(0, uri.lastIndexOf("."));
			uri = uri.substring(0, uri.lastIndexOf("."));
			if (uri.startsWith("jar:file")) {
				uri = uri.replaceFirst("jar:file", "zip");
			}
			dataSource = new SingleConnectionDataSource("jdbc:h2:" + uri, true);
			HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
			LocalContainerEntityManagerFactoryBean entityFactory = new LocalContainerEntityManagerFactoryBean();
			entityFactory.setJpaVendorAdapter(hibernateJpaVendorAdapter);
			entityFactory.setDataSource(dataSource);
			entityFactory.setPackagesToScan("org.gw.ylc.base.data.model");
			Properties properties = new Properties();
			properties.put("hibernate.hbm2ddl.auto", "update");
			properties.put("hibernate.dialect", "org.hibernate.dialect.H2Dialect");
			entityFactory.setJpaProperties(properties);
			entityFactory.afterPropertiesSet();
			EntityManagerFactory object = entityFactory.getObject();
			EntityManager entityManager = object.createEntityManager();
			TypedQuery<Permission> query = entityManager.createQuery("select p from Permission p", Permission.class);
			List<Permission> list = query.getResultList();
			for (Permission p : list) {
				EntityPermission permission = new EntityPermission();
				permission.setEntity(p.getEntity());
				permission.setRole(p.getRole());
				permission.setCanCreate(p.isCanCreate());
				permission.setCanRemove(p.isCanRemove());
				List<PropertyDef> props = p.getAccessDefs();
				for (PropertyDef def : props) {
					PropertyAccess access = new PropertyAccess();
					access.setAvailableExpr(def.getAvailableExpr());
					access.setDefaultValueExpr(def.getDefaultValueExpr());
					access.setName(def.getName());
					access.setReadable(def.isReadable());
					access.setWritable(def.isWritable());
					permission.getAccessMap().put(access.getName(), access);
				}

				List<QFilter> filters = p.getFilters();
				for (QFilter def : filters) {
					permission.getFilters().add(QFilter.toQueryFilter(def));
				}
				setPermission(permission);
			}
		} catch (Exception e) {
			throw e;
		} finally {
			if (dataSource != null)
				dataSource.destroy();
		}

	}

	@Override
	public void afterPropertiesSet() throws Exception {
		loadFromDB();
	}

	public String getDbFile() {
		return dbFile;
	}

	public String getUri() {
		return uri;
	}

	public void setUri(String uri) {
		this.uri = uri;
	}

	public void setDbFile(String dbFile) {
		this.dbFile = dbFile;
	}

}
